// Tencent is pleased to support the open source community by making Mars available.
// Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.

// Licensed under the MIT License (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
// http://opensource.org/licenses/MIT

// Unless required by applicable law or agreed to in writing, software distributed under the License is
// distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific language governing permissions and
// limitations under the License.

/*
 * nat64_prefix_util.cpp
 *
 *  Created on: 2016年6月22日
 *      Author: wutianqiang
 */

#include "nat64_prefix_util.h"

#include "local_ipstack.h"
#include "mars/comm/network/getaddrinfo_with_timeout.h"
#include "platform_comm.h"
#include "strutil.h"
#include "unix_socket.h"
#include "xlogger/xlogger.h"

using namespace mars::comm;

static const uint8_t kWellKnownV4Addr1[4] = {192, 0, 0, 170};
static const uint8_t kWellKnownV4Addr2[4] = {192, 0, 0, 171};
static const uint8_t kOurDefineV4Addr[4] = {192, 0, 2, 1};

// insert 0 after first byte
static const uint8_t kWellKnownV4Addr1_index1[5] = {192, 0, 0, 0, 170};
static const uint8_t kWellKnownV4Addr2_index1[5] = {192, 0, 0, 0, 171};
static const uint8_t kOurDefineV4Addr_index1[5] = {192, 0, 0, 2, 1};

// insert 0 after second byte
static const uint8_t kWellKnownV4Addr1_index2[5] = {192, 0, 0, 0, 170};
static const uint8_t kWellKnownV4Addr2_index2[5] = {192, 0, 0, 0, 171};
static const uint8_t kOurDefineV4Addr_index2[5] = {192, 0, 0, 2, 1};

// insert 0 after third byte
static const uint8_t kWellKnownV4Addr1_index3[5] = {192, 0, 0, 0, 170};
static const uint8_t kWellKnownV4Addr2_index3[5] = {192, 0, 0, 0, 171};
static const uint8_t kOurDefineV4Addr_index3[5] = {192, 0, 2, 0, 1};

// static bool IsIPv4Addr(const std::string& _str) {
//	struct in_addr v4_addr= {0};
//	return socket_inet_pton(AF_INET, _str.c_str(), &v4_addr)==0; //1 for success, 0 for invalid ip, -1 for other
//error
//}
static size_t GetSuffixZeroCount(uint8_t* _buf, size_t _buf_len) {
    size_t zero_count = 0;
    for (size_t i = 0; i < _buf_len; i++) {
        if ((uint8_t)0 == _buf[_buf_len - 1 - i])
            zero_count++;
        else
            break;
    }
    return zero_count;
}
static bool IsNat64AddrValid(const struct in6_addr* _replaced_nat64_addr) {
    bool is_iOS_above_9_2 = false;
#ifdef __APPLE__
    if (publiccomponent_GetSystemVersion() >= 9.2f)
        is_iOS_above_9_2 = true;
#endif
    size_t suffix_zero_count = GetSuffixZeroCount((uint8_t*)_replaced_nat64_addr, sizeof(struct in6_addr));
    if (0 != suffix_zero_count) {
        xwarn2(TSF "suffix_zero_count=%_, _replaced_nat64_addr=%_",
               suffix_zero_count,
               strutil::Hex2Str((char*)_replaced_nat64_addr, sizeof(struct in6_addr)));
    }
    bool is_valid = false;
    switch (suffix_zero_count) {
        case 3:
            // Pref64::/64
            if (is_iOS_above_9_2) {
                if (0 == memcmp(((uint8_t*)_replaced_nat64_addr) + 9, kOurDefineV4Addr, 4)) {
                    is_valid = true;
                }
            } else {
                if (0 == memcmp(((uint8_t*)_replaced_nat64_addr) + 9, kWellKnownV4Addr1, 4)
                    || 0 == memcmp(((uint8_t*)_replaced_nat64_addr) + 9, kWellKnownV4Addr2, 4)) {
                    is_valid = true;
                }
            }
            break;
        case 4:
            // Pref64::/56
            if (is_iOS_above_9_2) {
                if (0 == memcmp(((uint8_t*)_replaced_nat64_addr) + 7, kOurDefineV4Addr_index1, 5)) {
                    is_valid = true;
                }
            } else {
                if (0 == memcmp(((uint8_t*)_replaced_nat64_addr) + 7, kWellKnownV4Addr1_index1, 5)
                    || 0 == memcmp(((uint8_t*)_replaced_nat64_addr) + 7, kWellKnownV4Addr2_index1, 5)) {
                    is_valid = true;
                }
            }
            break;
        case 5:
            // Pref64::/48
            if (is_iOS_above_9_2) {
                if (0 == memcmp(((uint8_t*)_replaced_nat64_addr) + 6, kOurDefineV4Addr_index2, 5)) {
                    is_valid = true;
                }
            } else {
                if (0 == memcmp(((uint8_t*)_replaced_nat64_addr) + 6, kWellKnownV4Addr1_index2, 5)
                    || 0 == memcmp(((uint8_t*)_replaced_nat64_addr) + 6, kWellKnownV4Addr2_index2, 5)) {
                    is_valid = true;
                }
            }
            break;
        case 6:
            // Pref64::/40
            if (is_iOS_above_9_2) {
                if (0 == memcmp(((uint8_t*)_replaced_nat64_addr) + 5, kOurDefineV4Addr_index3, 5)) {
                    is_valid = true;
                }
            } else {
                if (0 == memcmp(((uint8_t*)_replaced_nat64_addr) + 5, kWellKnownV4Addr1_index3, 5)
                    || 0 == memcmp(((uint8_t*)_replaced_nat64_addr) + 5, kWellKnownV4Addr2_index3, 5)) {
                    is_valid = true;
                }
            }
            break;
        case 8:  // 7bytes suffix and 1 bytes u(RFC6052)
            // Pref64::/32
            if (is_iOS_above_9_2) {
                if (0 == memcmp(((uint8_t*)_replaced_nat64_addr) + 4, kOurDefineV4Addr, 4)) {
                    is_valid = true;
                }
            } else {
                if (0 == memcmp(((uint8_t*)_replaced_nat64_addr) + 4, kWellKnownV4Addr1, 4)
                    || 0 == memcmp(((uint8_t*)_replaced_nat64_addr) + 4, kWellKnownV4Addr2, 4)) {
                    is_valid = true;
                }
            }
            break;
        case 0:
            // Pref64::/96
            if (is_iOS_above_9_2) {
                if (0 == memcmp(((uint8_t*)_replaced_nat64_addr) + 12, kOurDefineV4Addr, 4)) {
                    is_valid = true;
                }
            } else {
                if (0 == memcmp(((uint8_t*)_replaced_nat64_addr) + 12, kWellKnownV4Addr1, 4)
                    || 0 == memcmp(((uint8_t*)_replaced_nat64_addr) + 12, kWellKnownV4Addr2, 4)) {
                    is_valid = true;
                }
            }
            break;
        default:
            xassert2(false, TSF "suffix_zero_count=%_", suffix_zero_count);
    }
    return is_valid;
}
static void ReplaceNat64WithV4IP(struct in6_addr* _replaced_nat64_addr, const struct in_addr* _v4_addr) {
    size_t suffix_zero_count = GetSuffixZeroCount((uint8_t*)_replaced_nat64_addr, sizeof(struct in6_addr));
    uint8_t zero = (uint8_t)0;
    switch (suffix_zero_count) {
        case 3:
            // Pref64::/64
            memcpy(((uint8_t*)_replaced_nat64_addr) + 9, (uint8_t*)_v4_addr, 4);
            break;
        case 4:
            // Pref64::/56
            memcpy(((uint8_t*)_replaced_nat64_addr) + 7, (uint8_t*)_v4_addr, 1);
            memcpy(((uint8_t*)_replaced_nat64_addr) + 8, &zero, 1);
            memcpy(((uint8_t*)_replaced_nat64_addr) + 9, ((uint8_t*)_v4_addr) + 1, 3);

            break;
        case 5:
            // Pref64::/48
            memcpy(((uint8_t*)_replaced_nat64_addr) + 6, (uint8_t*)_v4_addr, 2);
            memcpy(((uint8_t*)_replaced_nat64_addr) + 8, &zero, 1);
            memcpy(((uint8_t*)_replaced_nat64_addr) + 9, ((uint8_t*)_v4_addr) + 2, 2);
            break;
        case 6:
            // Pref64::/40
            memcpy(((uint8_t*)_replaced_nat64_addr) + 5, (uint8_t*)_v4_addr, 3);
            memcpy(((uint8_t*)_replaced_nat64_addr) + 8, &zero, 1);
            memcpy(((uint8_t*)_replaced_nat64_addr) + 9, ((uint8_t*)_v4_addr) + 3, 1);
            break;
        case 8:
            // Pref64::/32
            memcpy(((uint8_t*)_replaced_nat64_addr) + 4, (uint8_t*)_v4_addr, 4);
            break;
        case 0:
            // Pref64::/96
            memcpy(((uint8_t*)_replaced_nat64_addr) + 12, (uint8_t*)_v4_addr, 4);
            break;
        default:
            memcpy(((uint8_t*)_replaced_nat64_addr) + 12, (uint8_t*)_v4_addr, 4);
            xassert2(false, TSF "suffix_zero_count=%_", suffix_zero_count);
    }
}

bool ConvertV4toNat64V6(const struct in_addr& _v4_addr, struct in6_addr& _v6_addr) {
    xdebug_function();
    if (ELocalIPStack_IPv6 != local_ipstack_detect()) {
        xwarn2(TSF "Current Network is not ELocalIPStack_IPv6, no need GetNetworkNat64Prefix.");
        return false;
    }
    struct ::addrinfo hints, *res = NULL, *res0 = NULL;
    int error = 0;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = PF_INET6;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_ADDRCONFIG;

    char v4_ip[16] = {0};
    socket_inet_ntop(AF_INET, &_v4_addr, v4_ip, sizeof(v4_ip));
    bool is_timeout = false;
#ifdef __APPLE__
    if (publiccomponent_GetSystemVersion() >= 9.2f) {  // higher than iOS9.2
        error = getaddrinfo_with_timeout(v4_ip, NULL, &hints, &res0, is_timeout, 2000);
    } else {  // lower than iOS9.2 or other platform
#endif
        error = mars::comm::getaddrinfo_with_timeout("ipv4only.arpa", NULL, &hints, &res0, is_timeout, 2000);
#ifdef __APPLE__
    }
#endif

    bool ret = false;
    if (error == 0) {
        for (res = res0; res; res = res->ai_next) {
            char ip_buf[64] = {0};

            if (AF_INET6 == res->ai_family) {
#ifdef __APPLE__
                if (publiccomponent_GetSystemVersion() >= 9.2f) {  // higher than iOS9.2
                    // copy all 16 bytes
                    memcpy((char*)&_v6_addr, (char*)&((((sockaddr_in6*)res->ai_addr)->sin6_addr).s6_addr32), 16);
                    ret = true;
                    break;
                } else {  // lower than iOS9.2 or other platform
#endif

                    if (IsNat64AddrValid((struct in6_addr*)&(((sockaddr_in6*)res->ai_addr)->sin6_addr))) {
                        ReplaceNat64WithV4IP((struct in6_addr*)&(((sockaddr_in6*)res->ai_addr)->sin6_addr), &_v4_addr);
#ifdef WIN32
                        memcpy((char*)&_v6_addr, (char*)&((((sockaddr_in6*)res->ai_addr)->sin6_addr).u), 16);
#else
                    memcpy((char*)&_v6_addr, (char*)&((((sockaddr_in6*)res->ai_addr)->sin6_addr).s6_addr16), 16);
#endif
                        const char* ip_str = socket_inet_ntop(AF_INET6, &_v6_addr, ip_buf, sizeof(ip_buf));
                        xdebug2(TSF "AF_INET6 v4_ip=%_, nat64 ip_str = %_", v4_ip, ip_str);
                        ret = true;
                        break;
                    } else {
                        xerror2(TSF "Nat64 addr invalid, =%_",
                                strutil::Hex2Str((char*)&(((sockaddr_in6*)res->ai_addr)->sin6_addr), 16));
                        ret = false;
                    }
#ifdef __APPLE__
                }
#endif

            } else if (AF_INET == res->ai_family) {
                const char* ip_str =
                    socket_inet_ntop(AF_INET, &(((sockaddr_in*)res->ai_addr)->sin_addr), ip_buf, sizeof(ip_buf));
                xinfo2(TSF "AF_INET ip_str = %_", ip_str);
                ret = false;
            } else {
                xerror2(TSF "invalid ai_family = %_", res->ai_family);
                ret = false;
            }
        }
    } else {
        xerror2(TSF " getaddrinfo error = %_, res0:@%_", error, res0);
        ret = false;
    }
    if (NULL != res0)
        freeaddrinfo(res0);
    return ret;
}

bool ConvertV4toNat64V6(const std::string& _v4_ip, std::string& _nat64_v6_ip) {
    struct in_addr v4_addr = {0};
    int pton_ret = socket_inet_pton(AF_INET, _v4_ip.c_str(), &v4_addr);
    if (0 == pton_ret) {
        xwarn2(TSF "param error. %_ is not v4 ip", _v4_ip.c_str());
        return false;
    }

    struct in6_addr v6_addr = {{{0}}};
    if (ConvertV4toNat64V6(v4_addr, v6_addr)) {
        char v6_ip[64] = {0};
        socket_inet_ntop(AF_INET6, &v6_addr, v6_ip, sizeof(v6_ip));
        _nat64_v6_ip = std::string(v6_ip);
        return true;
    }
    return false;
}

///----------------------------------------------------------------------------
bool GetNetworkNat64Prefix(struct in6_addr& _nat64_prefix_in6) {
    xdebug_function();
    if (ELocalIPStack_IPv6 != local_ipstack_detect()) {
        xwarn2(TSF "Current Network is not ELocalIPStack_IPv6, no need GetNetworkNat64Prefix.");
        return false;
    }
    struct ::addrinfo hints, *res = NULL, *res0 = NULL;
    int error = 0;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = PF_INET6;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_ADDRCONFIG;

    bool ret = false;
#ifdef __APPLE__
    if (publiccomponent_GetSystemVersion() >= 9.2f) {
        error = getaddrinfo("192.0.2.1", NULL, &hints, &res0);
    } else {
#endif
        error = getaddrinfo("ipv4only.arpa", NULL, &hints, &res0);
#ifdef __APPLE__
    }
#endif
    if (error == 0) {
        for (res = res0; res; res = res->ai_next) {
            char ip_buf[64] = {0};

            if (AF_INET6 == res->ai_family) {
#ifdef WIN32
                memcpy((char*)&(_nat64_prefix_in6.u), (char*)&((((sockaddr_in6*)res->ai_addr)->sin6_addr).u), 12);
#else
                memcpy((char*)&(_nat64_prefix_in6.s6_addr16),
                       (char*)&((((sockaddr_in6*)res->ai_addr)->sin6_addr).s6_addr16),
                       12);
#endif
                ret = true;
                break;

            } else if (AF_INET == res->ai_family) {
                const char* ip_str =
                    socket_inet_ntop(AF_INET, &(((sockaddr_in*)res->ai_addr)->sin_addr), ip_buf, sizeof(ip_buf));
                xinfo2(TSF "AF_INET ip_str = %_", ip_str);
                ret = false;
            } else {
                xerror2(TSF "invalid ai_family = %_", res->ai_family);
                ret = false;
            }
        }
    } else {
        xerror2(TSF " getaddrinfo error = %_, res0:@%_", error, res0);
        ret = false;
    }

    if (NULL != res0)
        freeaddrinfo(res0);
    return ret;
}

bool GetNetworkNat64Prefix(std::string& _nat64_prefix) {
    struct in6_addr nat64_prefix_in6;
    memset(&nat64_prefix_in6, 0, sizeof(nat64_prefix_in6));

    if (GetNetworkNat64Prefix(nat64_prefix_in6)) {
        char ip_buf[64] = {0};
        const char* prefix_str = socket_inet_ntop(AF_INET6, &nat64_prefix_in6, ip_buf, sizeof(ip_buf));
        _nat64_prefix = std::string(prefix_str);
        return true;
    }
    return false;
}
